`timescale 1ns/1ps

// CCD驱动状态机模块

module ccd_drv_sm (
    input clk_1M,           // 状态机的系统时钟,默认采用1MHz
    input clk_reg,          // 对应1个像元周期的时钟
    input sm_trig,          // 触发状态机开始工作的信号
    
    input[15:0] vtrans_num,
    input[15:0] htrans_num,

    input vtrans_done,
    input htrans_done,
    output vtrans_en,
    output htrans_en,
    output bias_ctr,
    output status_vtrans,   // tell us if a vertical line transfer is finished
    output status_htrans    // tell us if all registers of a row of pixels havs been readout
);

//  状态的定义（迁移条件见下面）
    localparam IDLE         = 4'd0; // 空闲状态
    
    //  第一行空读（直接进行水平转移，不先经过1次垂直转移）
    localparam S_HT0_PRE    = 4'd1; // 空读准备
    localparam S_HT0        = 4'd2; // 空读
    localparam S_HT0_POS    = 4'd3; // 空读结束
    
    // 水平转移
    localparam S_HT_PRE     = 4'd4; // 水平转移准备
    localparam S_HT         = 4'd5; // 水平转移
    localparam S_HT_POS     = 4'd6; // 水平转移结束（可能进入垂直转移准备或休眠状态）
    
    // 垂直转移（像元读出）
    localparam S_VT_PRE     = 4'd7; // 垂直转移准备
    localparam S_VT         = 4'd8; // 垂直转移
    localparam S_VT_POS     = 4'd9; // 垂直转移结束（可能继续进入水平转移准备）
    
    localparam S_SLEEP      = 4'd10;

//  状态寄存器
    reg[3:0] state_c = S_SLEEP;
    reg[3:0] state_n;


    reg[15:0] vtrans_cnt;   //  vertial transfer number counts
    reg[15:0] htrans_cnt;   //  horizontal transfer number counts

//  ===========================================================================
//  状态迁移条件

    wire idle2ht0pre;       // ILDE -> S_HT0_PRE

    wire ht0pre2ht0;        // S_HT0_PRE --> S_HT0
    wire ht02ht0pos;        // S_HT0 --> S_HT0_POS
    wire ht0pos2vtpre;      // S_HT0_POS --> S_VT_PRE
    
    wire htpre2ht;
    wire ht2htpos;
    wire htpos2vtpre;
    
    wire vtpre2vt;
    wire vt2vtpos;
    wire vtpos2htpre;
    
    wire htpos2idle;        
    wire htpos2sleep;       //当所有读出完成之后，进入sleep状态
//  ===========================================================================

//  同步时序描述“次态”迁移到“现态”，以像元周期为基本时间单位
    // always @(posedge clk_1M or posedge sm_trig) begin // 用上升沿触发会导致问题！！！
    always @(negedge clk_1M or posedge sm_trig) begin
        if (sm_trig) begin
            state_c <= IDLE;
        end
        else begin 
            state_c <= state_n;
        end
    end

//  状态转换条件
    assign idle2ht0pre  = (state_c == IDLE);                                    // ILDE -> S_RT0_PRE
    
    assign ht0pre2ht0   = (state_c == S_HT0_PRE);                               // S_RT0_PRE --> S_RT0
    assign ht02ht0pos   = (state_c == S_HT0) && (htrans_cnt == htrans_num + 3); // S_RT0 --> S_RT0_POS
    assign ht0pos2vtpre = (state_c == S_HT0_POS);                               // S_RT0_POS --> S_LT_PRE
    
    assign vtpre2vt     = (state_c == S_VT_PRE) ;
    assign vt2vtpos     = (state_c == S_VT) && (vtrans_done == 1'b1);
    assign vtpos2htpre  = (state_c == S_VT_POS) ;
    
    assign htpre2ht     = (state_c == S_HT_PRE) ;
    assign ht2htpos     = (state_c == S_HT) && (htrans_cnt == htrans_num + 3);
    assign htpos2vtpre  = (state_c == S_HT_POS) && (vtrans_cnt < vtrans_num);
    
    assign htpos2idle   = (state_c == S_HT_POS) && (vtrans_cnt == vtrans_num);
    assign htpos2sleep  = (state_c == S_HT_POS) && (vtrans_cnt == vtrans_num);


//  时序逻辑描述状态转移条件判断
    always@(*) begin
        case( state_c )
            IDLE: begin
                if( idle2ht0pre ) begin
                    state_n = S_HT0_PRE;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0_PRE: begin
                if( ht0pre2ht0 ) begin
                    state_n = S_HT0;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0: begin
                if( ht02ht0pos ) begin
                    state_n = S_HT0_POS;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0_POS: begin
                if( ht0pos2vtpre ) begin
                    state_n = S_VT_PRE;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_VT_PRE: begin
                if( vtpre2vt ) begin
                    state_n = S_VT;
                end
                else begin
                    state_n = state_c;
                end
            end

            S_VT: begin
                if( vt2vtpos ) begin
                    state_n = S_VT_POS;
                end 
                else begin
                    state_n = state_c;
                end     
            end

            S_VT_POS: begin
                if( vtpos2htpre ) begin
                    state_n = S_HT_PRE;
                end 
                else begin
                    state_n = state_c;
                end     
            end

            S_HT_PRE: begin
                if( htpre2ht ) begin
                    state_n = S_HT;
                end
                else begin
                    state_n = state_c;
                end
            end

            S_HT: begin
                if( ht2htpos ) begin
                    state_n = S_HT_POS;
                end 
                else begin
                    state_n = state_c;
                end     
            end

            S_HT_POS: begin // 现在像元读出结束后要么进入下一个行转移，要么进入SLEEP
                if( htpos2vtpre ) begin
                    state_n = S_VT_PRE;
                end 
                else if( htpos2sleep ) begin
                    state_n = S_SLEEP;
                end
            end
            
            S_SLEEP: begin
                state_n = S_SLEEP;
            end

            default: begin
                state_n = S_SLEEP;
            end
        endcase
    end

    assign htrans_en = (state_c == S_HT) || (state_c == S_HT0);
    assign vtrans_en = (state_c == S_VT);
    assign bias_ctr  = (state_c == S_VT);

// 水平读出计数器htrans_cnt
    // always@(posedge clk_reg or posedge sm_trig) begin
    always@(negedge clk_reg or posedge sm_trig) begin
        if(sm_trig) begin
            htrans_cnt <= 16'd0;
        end
        else if( state_c == S_HT_PRE || state_c == S_HT || state_c == S_HT0_PRE || state_c == S_HT0 ) begin
            htrans_cnt <= htrans_cnt + 16'd1;
        end
        else if( state_c == S_VT_PRE || state_c == IDLE ) begin
            htrans_cnt <= 16'd0;   //在每一行数据读出完成之后或者回到IDLE状态时，需要归零！！！！
        end
    end

    assign status_htrans= (state_c == S_HT_POS) && ( htrans_cnt == (htrans_num + 3) ); // 注意最后的+3
    assign status_vtrans= (state_c == S_VT_POS);

//  垂直转移计数器vtrans_cnt
    // always@(posedge clk_1M or posedge sm_trig) begin
    always@(negedge clk_1M or posedge sm_trig) begin
        if(sm_trig) begin
            vtrans_cnt <= 16'd0;   // 仅在sm_trig信号拉高时将行计数器归零
        end
        else if( state_c == S_VT_POS ) begin
            vtrans_cnt <= vtrans_cnt + 16'd1;
        end
        // 注意：除非reset，否则不能归零！！！！
    end
endmodule